Skip to content

Commit

Permalink
generator port done
Browse files Browse the repository at this point in the history
  • Loading branch information
neuecc committed Dec 17, 2024
1 parent a45053b commit 4649014
Show file tree
Hide file tree
Showing 4 changed files with 226 additions and 5 deletions.
5 changes: 5 additions & 0 deletions sandbox/GeneratorSandbox/GeneratorSandbox.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="MessagePack" Version="3.1.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\MasterMemory.Annotations\MasterMemory.Annotations.csproj" />
<ProjectReference Include="..\..\src\MasterMemory.SourceGenerator\MasterMemory.SourceGenerator.csproj">
<OutputItemType>Analyzer</OutputItemType>
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
Expand Down
70 changes: 70 additions & 0 deletions sandbox/GeneratorSandbox/Program.cs
Original file line number Diff line number Diff line change
@@ -1,2 +1,72 @@
// See https://aka.ms/new-console-template for more information
using MasterMemory;
using MessagePack;

Console.WriteLine("Hello, World!");









public enum Gender
{
Male, Female, Unknown
}

[MemoryTable("person"), MessagePackObject(true)]
public class Person
{
[PrimaryKey(keyOrder: 1)]
public int PersonId { get; set; }
[SecondaryKey(0), NonUnique]
[SecondaryKey(2, keyOrder: 1), NonUnique]
public int Age { get; set; }
[SecondaryKey(1), NonUnique]
[SecondaryKey(2, keyOrder: 0), NonUnique]
public Gender Gender { get; set; }
public string Name { get; set; }

public Person() // ?
{
}

public Person(int PersonId, int Age, Gender Gender, string Name)
{
this.PersonId = PersonId;
this.Age = Age;
this.Gender = Gender;
this.Name = Name;
}

public override string ToString()
{
return $"{PersonId} {Age} {Gender} {Name}";
}
}

[MemoryTable("monster"), MessagePackObject(true)]
public class Monster
{
[PrimaryKey]
public int MonsterId { get; private set; }
public string Name { get; private set; }
public int MaxHp { get; private set; }

public Monster(int MonsterId, string Name, int MaxHp)
{
this.MonsterId = MonsterId;
this.Name = Name;
this.MaxHp = MaxHp;
}
}

[MemoryTable("enumkeytable"), MessagePackObject(true)]
public class EnumKeyTable
{
[PrimaryKey]
public Gender Gender { get; set; }
}
71 changes: 67 additions & 4 deletions src/MasterMemory.SourceGenerator/GeneratorCore/CodeGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ static string WriteToFile(string directory, string fileName, string content, boo
{
var path = Path.Combine(directory, fileName + ".cs");
var contentBytes = Encoding.UTF8.GetBytes(NormalizeNewLines(content));

// If the generated content is unchanged, skip the write.
if (!forceOverwrite && File.Exists(path))
{
Expand Down Expand Up @@ -196,7 +196,70 @@ IEnumerable<GenerationContext> CreateGenerationContext(string filePath)
}
}

(PrimaryKey, List<SecondaryKey>, PropertyDeclarationSyntax) ExtractPropertyAttribute(PropertyDeclarationSyntax property)
public static GenerationContext CreateGenerationContext2(ClassDeclarationSyntax classDecl)
{
var root = classDecl.SyntaxTree.GetRoot();

var ns = root.DescendantNodes().OfType<NamespaceDeclarationSyntax>()
.Select(x => "using " + x.Name.ToFullStringTrim() + ";")
.ToArray();

var usingStrings = root.DescendantNodes()
.OfType<UsingDirectiveSyntax>()
.Select(x => x.ToFullString().Trim())
.Concat(new[] { "using MasterMemory", "using MasterMemory.Validation", "using System", "using System.Collections.Generic" })
.Concat(ns)
.Select(x => x.Trim(';') + ";")
.Distinct()
.OrderBy(x => x, StringComparer.Ordinal)
.ToArray();

var context = new GenerationContext();

foreach (var attr in classDecl.AttributeLists.SelectMany(x => x.Attributes))
{
var attrName = attr.Name.ToFullString().Trim();
if (attrName == "MemoryTable" || attrName == "MasterMemory.Annotations.MemoryTable")
{
context.ClassName = classDecl.Identifier.ToFullString().Trim();
context.MemoryTableName = AttributeExpressionToString(attr.ArgumentList.Arguments[0].Expression) ?? context.ClassName;

var members = classDecl.Members.OfType<PropertyDeclarationSyntax>()
.Select(x => ExtractPropertyAttribute(x))
.ToArray();

var primaryKey = AggregatePrimaryKey(members.Where(x => x.Item1 != null).Select(x => x.Item1));
if (primaryKey.Properties.Length == 0)
{
throw new InvalidOperationException("MemoryTable does not found PrimaryKey property, Type:" + context.ClassName);
}

var secondaryKeys = members.SelectMany(x => x.Item2).GroupBy(x => x.IndexNo).Select(x => AggregateSecondaryKey(x)).ToArray();
var properties = members.Where(x => x.Item3 != null).Select(x => new Property
{
Type = x.Item3.Type.ToFullStringTrim(),
Name = x.Item3.Identifier.Text,
}).ToArray();

context.PrimaryKey = primaryKey;
context.SecondaryKeys = secondaryKeys;
context.Properties = properties;
}
}

if (context.PrimaryKey != null)
{
context.UsingStrings = usingStrings;
context.OriginalClassDeclaration = classDecl;
// context.InputFilePath = filePath;
return context;
}

return null!; // if null????
}


static (PrimaryKey, List<SecondaryKey>, PropertyDeclarationSyntax) ExtractPropertyAttribute(PropertyDeclarationSyntax property)
{
// Attribute Parterns:
// Primarykey(keyOrder = 0)
Expand Down Expand Up @@ -309,7 +372,7 @@ IEnumerable<GenerationContext> CreateGenerationContext(string filePath)
return (resultPrimaryKey, secondaryKeys, isSerializableProperty ? property : null);
}

PrimaryKey AggregatePrimaryKey(IEnumerable<PrimaryKey> primaryKeys)
static PrimaryKey AggregatePrimaryKey(IEnumerable<PrimaryKey> primaryKeys)
{
var primarykey = new PrimaryKey();
var list = new List<KeyProperty>();
Expand All @@ -328,7 +391,7 @@ PrimaryKey AggregatePrimaryKey(IEnumerable<PrimaryKey> primaryKeys)
}

// grouped by IndexNo.
SecondaryKey AggregateSecondaryKey(IGrouping<int, SecondaryKey> secondaryKeys)
static SecondaryKey AggregateSecondaryKey(IGrouping<int, SecondaryKey> secondaryKeys)
{
var secondaryKey = new SecondaryKey();
secondaryKey.IndexNo = secondaryKeys.Key;
Expand Down
85 changes: 84 additions & 1 deletion src/MasterMemory.SourceGenerator/MasterMemoryGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
using MasterMemory.GeneratorCore;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;

namespace MasterMemory.SourceGenerator;

Expand All @@ -16,11 +21,89 @@ public partial class MasterMemoryGenerator : IIncrementalGenerator
public bool ReturnNullIfKeyNotFound { get; set; } = true;
public bool ForceOverwrite { get; set; }

//[Option("i", "Input file directory(search recursive).")]
//string inputDirectory,

// [Option("o", "Output file directory.")]string outputDirectory,

// [Option("n", "Namespace of generated files.")]string usingNamespace,

// [Option("p", "Prefix of class names.")]string prefixClassName = "",

// [Option("c", "Add immutable constructor to MemoryTable class.")]bool addImmutableConstructor = false,

// [Option("t", "Return null if key not found on unique find method.")]bool returnNullIfKeyNotFound = false,

// [Option("f", "Overwrite generated files if the content is unchanged.")]bool forceOverwrite = false)

public void Initialize(IncrementalGeneratorInitializationContext context)
{
// TODO:...
// Input and Output is no needed.
// prefix should be configurable?(to assemblyattribute)
// immutableconstructor
// returnnull

// new CodeGenerator().GenerateFile(UsingNamespace, InputDirectory, OutputDirectory, PrefixClassName, AddImmutableConstructor, !ReturnNullIfKeyNotFound, ForceOverwrite, x => Console.WriteLine(x));

var memoryTables = context.SyntaxProvider.ForAttributeWithMetadataName("MasterMemory.MemoryTableAttribute",
(node, token) => true,
(ctx, token) => ctx)
.Collect();

context.RegisterSourceOutput(memoryTables, EmitMemoryTable);
}

void EmitMemoryTable(SourceProductionContext context, ImmutableArray<GeneratorAttributeSyntaxContext> memoryTables)
{
var usingNamespace = ""; // TODO:from option?
var prefixClassName = ""; // TODO

new CodeGenerator().GenerateFile(UsingNamespace, InputDirectory, OutputDirectory, PrefixClassName, AddImmutableConstructor, !ReturnNullIfKeyNotFound, ForceOverwrite, x => Console.WriteLine(x));
var list = memoryTables.Select(x =>
{
// TODO: RecordDeclaration
var classDecl = x.TargetNode as ClassDeclarationSyntax;
return CodeGenerator.CreateGenerationContext2(classDecl!);
})
.ToList();

list.Sort((a, b) => string.Compare(a.ClassName, b.ClassName, StringComparison.Ordinal));

var usingStrings = string.Join(Environment.NewLine, list.SelectMany(x => x.UsingStrings).Distinct().OrderBy(x => x, StringComparer.Ordinal));

var builderTemplate = new DatabaseBuilderTemplate();
var databaseTemplate = new MemoryDatabaseTemplate();
var immutableBuilderTemplate = new ImmutableBuilderTemplate();
var resolverTemplate = new MessagePackResolverTemplate();
builderTemplate.Namespace = databaseTemplate.Namespace = immutableBuilderTemplate.Namespace = resolverTemplate.Namespace = usingNamespace;
builderTemplate.PrefixClassName = databaseTemplate.PrefixClassName = immutableBuilderTemplate.PrefixClassName = resolverTemplate.PrefixClassName = prefixClassName;
builderTemplate.Using = databaseTemplate.Using = immutableBuilderTemplate.Using = resolverTemplate.Using = (usingStrings + Environment.NewLine + ("using " + usingNamespace + ".Tables;"));
builderTemplate.GenerationContexts = databaseTemplate.GenerationContexts = immutableBuilderTemplate.GenerationContexts = resolverTemplate.GenerationContexts = list.ToArray();

Log(WriteToFile(context, builderTemplate.ClassName, builderTemplate.TransformText()));
Log(WriteToFile(context, immutableBuilderTemplate.ClassName, immutableBuilderTemplate.TransformText()));
Log(WriteToFile(context, databaseTemplate.ClassName, databaseTemplate.TransformText()));
Log(WriteToFile(context, resolverTemplate.ClassName, resolverTemplate.TransformText()));

// TODO:Modify of SourceGenerator.GenerateFile
}

static void Log(string msg)
{
Trace.WriteLine(msg);
}

static string NormalizeNewLines(string content)
{
// The T4 generated code may be text with mixed line ending types. (CR + CRLF)
// We need to normalize the line ending type in each Operating Systems. (e.g. Windows=CRLF, Linux/macOS=LF)
return content.Replace("\r\n", "\n").Replace("\n", Environment.NewLine);
}

static string WriteToFile(SourceProductionContext context, string fileName, string content)
{
var contentString = NormalizeNewLines(content);
context.AddSource($"MasterMemory.{fileName}.g.cs", contentString);
return $"Generate {fileName}.";
}
}

0 comments on commit 4649014

Please sign in to comment.